using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace Dida.Tool
{
	[AttributeUsage(AttributeTargets.Property,AllowMultiple = false)]
	public class DOMChildCollectionAttribute : Attribute
	{
		string collectionName = "";

		public string CollectionName
		{
			get { return collectionName; }
			set { collectionName = value; }
		}
	}

	[AttributeUsage(AttributeTargets.Property,AllowMultiple = false)]
	public class DOMChildObjectAttribute : Attribute
	{
	}

	public interface IDOMCollection : IList
	{
		event ItemAddedHandler ItemAdded;
		event ItemRemovedHandler ItemRemoved;
		event ItemNameChangedHandler ItemNameChanged;
	}

	/// <summary>
	/// A collection that's used in Document Object Model representations.
	/// It contains functionality that comes in handy in tools for
	/// editing a DOM
	/// </summary>
	public interface IDOMCollection<T> : IList<T>, IDOMCollection
		where T : DOMObj
	{
		new event ItemAddedHandler<T> ItemAdded;
		new event ItemRemovedHandler<T> ItemRemoved;
		new event ItemNameChangedHandler<T> ItemNameChanged;
	}

	public class ItemAddedArgs : EventArgs
	{
		IDOMObj newItem;
		int index;

		public ItemAddedArgs(IDOMObj newItem, int index)
		{
			this.newItem = newItem;
			this.index = index;
		}

		public IDOMObj NewItem
		{
			get { return newItem; }
		}

		public int Index
		{
			get { return index; }
		}
	}

	public class ItemAddedArgs<T> : ItemAddedArgs
		where T : IDOMObj 
	{
		public ItemAddedArgs(T newItem, int index)
			: base(newItem,index)
		{
		}

		public new T NewItem
		{
			get { return (T)base.NewItem; }
		}
	}

	public delegate void ItemAddedHandler(object sender,ItemAddedArgs args);
	public delegate void ItemAddedHandler<ItemType>(object sender, ItemAddedArgs<ItemType> args)
		where ItemType : IDOMObj;

	public class ItemRemovedArgs : EventArgs
	{
		IDOMObj removedItem;
		int index;

		public ItemRemovedArgs(IDOMObj removedItem, int index)
		{
			this.removedItem = removedItem;
			this.index = index;
		}

		public IDOMObj RemovedItem
		{
			get { return removedItem; }
		}

		public int Index
		{
			get { return index; }
		}
	}

	public class ItemRemovedArgs<T> : ItemRemovedArgs
		where T : IDOMObj
	{
		public ItemRemovedArgs(T removedItem, int index)
			: base(removedItem, index)
		{
		}

		public new T RemovedItem
		{
			get { return (T)base.RemovedItem; }
		}
	}

	public delegate void ItemRemovedHandler(object sender, ItemRemovedArgs args);
	public delegate void ItemRemovedHandler<ItemType>(object sender,ItemRemovedArgs<ItemType> args)
		where ItemType : IDOMObj;

	public class ItemNameChangedArgs : EventArgs
	{
		IDOMObj item;
		string newName;
		string oldName;

		public ItemNameChangedArgs(IDOMObj item, string newName, string oldName)
		{
			this.item = item;
			this.newName = newName;
			this.oldName = oldName;
		}

		public IDOMObj Item
		{
			get { return item; }
		}

		public string NewName
		{
			get { return newName; }
		}

		public string OldName
		{
			get { return oldName; }
		}
	}

	public class ItemNameChangedArgs<ItemType> : ItemNameChangedArgs
		where ItemType : IDOMObj
	{
		public ItemNameChangedArgs(ItemType item,string newName,string oldName)
			: base(item,newName,oldName)
		{
		}

		public new ItemType Item
		{
			get { return (ItemType)base.Item; }
		}
	}

	public delegate void ItemNameChangedHandler(object sender, ItemNameChangedArgs args);
	public delegate void ItemNameChangedHandler<ItemType>(object sender, ItemNameChangedArgs<ItemType> args)
		where ItemType : IDOMObj;
	
	public class DOMCollection<T> : IDOMCollection<T>
		where T : DOMObj
	{
		Dictionary<string, Handle<T>> byName = new Dictionary<string, Handle<T>>(
			new NameEqComparer());
		List<Handle<T>> ordered = new List<Handle<T>>();

		IDOMNamespace nameSpace;

		ItemAddedHandler nonGenericItemAddedHandlers = null;
		ItemRemovedHandler nonGenericItemRemovedHandlers = null;
		ItemNameChangedHandler nonGenericItemNameChangedHandlers = null;

		public DOMCollection()
		{
			nameSpace = new CollectionOnlyNamespace(this);
		}

		public IDOMNamespace NameSpace
		{
			get { return nameSpace; }
		}

		public DOMCollection(IDOMNamespace nameSpace)
		{
			this.nameSpace = nameSpace;
		}

		public virtual T Add(T obj)
		{
			if (nameSpace.IsNameUsed(obj.Name))
			{
				throw new Exception("There's already an object called \"" 
					+ obj.Name + "\".");
			}

			Handle<T> handle = new Handle<T>(obj);

			byName.Add(obj.Name, handle);
			ordered.Add(handle);
			nameSpace.RegisterName(obj.Name);

			obj.ValidateNameChange += ValidateNameChange;
			obj.NameChanged += ObjNameChanged;

			// invoke the events
			ItemAddedHandler<T> handlers = ItemAdded;
			if (handlers != null)
			{
				ItemAddedArgs<T> args = new ItemAddedArgs<T>(obj,ordered.Count - 1);
				handlers(this, args);
			}

			ItemAddedHandler ngHandlers = nonGenericItemAddedHandlers;
			if (ngHandlers != null)
			{
				ItemAddedArgs<T> args = new ItemAddedArgs<T>(obj, ordered.Count - 1);
				ngHandlers(this, args);
			}

			return obj;
		}

		void ICollection<T>.Add(T value)
		{
			Add(value);
		}

		int IList.Add(object value)
		{
			Add((T)value);
			return ordered.Count - 1;
		}

		public T this[string name]
		{
			get
			{
				Debug.Assert(byName.ContainsKey(name), 
					"There is no object called \"" + name + "\" in this collection");

				return byName[name].Value;
			}
		}

		public T TryGetValue(string name)
		{
			Handle<T> ret;
			if (byName.TryGetValue(name, out ret))
				return ret.Value;
			else
				return null;
		}

		private void ValidateNameChange(object sender, NameChangedArgs args)
		{
			// Test if the new name is already in use. Note that if the
			// name is already in use by this object, we do allow the change.
			// This is needed to allow the user to change the case of the name.
			if (nameSpace.IsNameUsed(args.newName) && 
				args.oldName.ToUpper() != args.newName.ToUpper())
			{
				throw new Exception("There's already an object called \"" + 
					args.newName + "\".");
			}
		}

		private void ObjNameChanged(object sender, NameChangedArgs args)
		{
			Debug.Assert(byName.ContainsKey(args.oldName),
				"This collection doesn't contain an item called \"" + args.oldName + "\"");

			Handle<T> obj = byName[args.oldName];
			byName.Remove(args.oldName);
			byName.Add(args.newName, obj);
			
			nameSpace.UnregisterName(args.oldName);
			nameSpace.RegisterName(args.newName);

			// call the ItemNameChanged event
			ItemNameChangedHandler<T> handler = ItemNameChanged;
			if (handler != null)
			{
				handler(this,new ItemNameChangedArgs<T>(
					obj.Value,args.newName,args.oldName));
			}

			ItemNameChangedHandler ngHandler = nonGenericItemNameChangedHandlers;
			if (ngHandler != null)
			{
				ngHandler(this, new ItemNameChangedArgs(
					obj.Value, args.newName, args.oldName));
			}
		}

		#region IDOMCollection<T> Members

		public event ItemAddedHandler<T> ItemAdded;
		public event ItemRemovedHandler<T> ItemRemoved;
		public event ItemNameChangedHandler<T> ItemNameChanged;

		event ItemAddedHandler IDOMCollection.ItemAdded
		{
			add	
			{
				nonGenericItemAddedHandlers = (ItemAddedHandler)Delegate.Combine(
					nonGenericItemAddedHandlers, value);
			}

			remove 
			{
				nonGenericItemAddedHandlers = (ItemAddedHandler)Delegate.Remove(
					nonGenericItemAddedHandlers, value);
			}
		}

		event ItemRemovedHandler IDOMCollection.ItemRemoved
		{
			add
			{
				nonGenericItemRemovedHandlers = (ItemRemovedHandler)Delegate.Combine(
					nonGenericItemRemovedHandlers, value);				
			}

			remove
			{
				nonGenericItemRemovedHandlers = (ItemRemovedHandler)Delegate.Remove(
					nonGenericItemRemovedHandlers, value);
			}
		}

		event ItemNameChangedHandler IDOMCollection.ItemNameChanged
		{
			add
			{
				nonGenericItemNameChangedHandlers = (ItemNameChangedHandler)Delegate.Combine(
					nonGenericItemNameChangedHandlers, value);
			}

			remove
			{
				nonGenericItemNameChangedHandlers = (ItemNameChangedHandler)Delegate.Remove(
					nonGenericItemNameChangedHandlers, value);
			}
		}

		public int IndexOf(T item)
		{
			for (int i = 0; i < ordered.Count; i++)
				if (ordered[i].Value == item)
					return i;
			return -1;
		}

		int IList.IndexOf(object item)
		{
			return IndexOf((T)item);
		}

		public virtual void Insert(int index, T item)
		{
			throw new Exception("The method or operation is not implemented.");
		}

		void IList.Insert(int index, object item)
		{
			Insert(index, (T)item);
		}

		public virtual void RemoveAt(int index)
		{
			Handle<T> toRemove = ordered[index];
			ordered.RemoveAt(index);

			byName.Remove(toRemove.Value.Name);
			
			// call the events
			ItemRemovedHandler<T> handler = ItemRemoved;
			if (handler != null)
			{
				ItemRemovedArgs<T> args = new ItemRemovedArgs<T>(toRemove.Value,index);
				handler(this, args);
			}
			ItemRemovedHandler ngHandler = nonGenericItemRemovedHandlers;
			if (ngHandler != null)
			{
				ItemRemovedArgs<T> args = new ItemRemovedArgs<T>(toRemove.Value, index);
				handler(this, args);
			}

			// set the handle to null, so "logical references" (references to 
			// objects that are not owned by the object that contains the reference) 
			// will now have a null handle
			toRemove.Value = null;
		}

		public virtual bool Remove(T item)
		{
			int index = IndexOf(item);
			if (index == -1)
				return false;

			RemoveAt(index);

			return true;
		}

		void IList.Remove(object item)
		{
			Remove((T)item);
		}

		public T this[int index]
		{
			get
			{
				return ordered[index].Value;
			}

			set
			{
				Debug.Fail("Not supported");
			}
		}

		object IList.this[int index]
		{
			get { return this[index]; }
			set { this[index] = (T)value; }				
		}

		public void Clear()
		{
			ItemRemovedHandler<T> handlers = ItemRemoved;
			ItemRemovedHandler ngHandlers = nonGenericItemRemovedHandlers;
			if (handlers != null || ngHandlers != null)
			{
				for (int i = 0; i < ordered.Count; i++)
				{
					ItemRemovedArgs<T> args = new ItemRemovedArgs<T>(
						ordered[i].Value,i);
					if(handlers != null)
						handlers(this, args);
					if (ngHandlers != null)
						ngHandlers(this, args);
				}
			}

			// set the handles to null
			foreach (Handle<T> handle in ordered)
				handle.Value = null;

			ordered.Clear();
			byName.Clear();
		}

		public bool Contains(T item)
		{
			Handle<T> found;
			if (byName.TryGetValue(item.Name, out found))
				return item == found.Value;
			else
				return false;
		}

		bool IList.Contains(object item)
		{
			return Contains((T)item);
		}

		public void CopyTo(T[] array, int arrayIndex)
		{
			foreach (Handle<T> item in ordered)
			{
				array[arrayIndex++] = item.Value;
			}
		}

		void ICollection.CopyTo(Array arr, int arrayIndex)
		{
			CopyTo((T[])arr, arrayIndex);
		}

		public int Count
		{
			get { return ordered.Count; }
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		public bool IsFixedSize
		{
			get { return false; }
		}

		public bool IsSynchronized
		{
			get { return false; }
		}

		public object SyncRoot
		{
			get { return this; }
		}

		public IEnumerator<T> GetEnumerator()
		{
			return new DerefHandleEnumerator<T>(ordered.GetEnumerator());
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new DerefHandleEnumerator<T>(ordered.GetEnumerator());
		}

		#endregion

		private class CollectionOnlyNamespace : IDOMNamespace
		{
			DOMCollection<T> collection;

			public CollectionOnlyNamespace(DOMCollection<T> collection)
			{
				this.collection = collection;
			}

			#region IDOMNamespace Members

			public bool IsNameUsed(string name)
			{
				return collection.byName.ContainsKey(name);
			}

			public void RegisterName(string name)
			{
				// nothing
			}

			public void UnregisterName(string name)
			{
				// nothing
			}

			public string CreateUniqueName(string baseName)
			{
				string ret;
				int index = 0;
				do
				{
					ret = baseName + "_" + index.ToString();
					index++;
				}
				while (collection.byName.ContainsKey(ret));

				return ret;
			}

			#endregion
		}
	}

	public class NameEqComparer : IEqualityComparer<string>
	{
		public int GetHashCode(string s)
		{
			return s.ToUpper().GetHashCode();
		}

		public bool Equals(string x, string y)
		{
			return x.ToUpper() == y.ToUpper();
		}
	}

	public class NameComparer : IComparer<string>, IComparer
	{
		public int Compare(string x, string y)
		{
			return x.ToUpper().CompareTo(y.ToUpper());
		}

		public int Compare(object x, object y)
		{
			return Compare((string)x, (string)y);
		}
	}

	public interface IDOMNamespace
	{
		bool IsNameUsed(string name);
		void RegisterName(string name);
		void UnregisterName(string name);

		string CreateUniqueName(string baseName);
	}

	public class DOMNamespace : IDOMNamespace
	{
		HashSet<string> usedNames = new HashSet<string>(new NameEqComparer());

		public bool IsNameUsed(string name)
		{
			return usedNames.Contains(name);
		}

		public void RegisterName(string name)
		{
			Debug.Assert(!IsNameUsed(name), "This name is already used");
			usedNames.Add(name);
		}

		public void UnregisterName(string name)
		{
			usedNames.Remove(name);
		}

		public string CreateUniqueName(string baseName)
		{
			string ret;
			int index = 1;
			do
			{
				ret = baseName + "_" + index.ToString();
				index++;
			}
			while (usedNames.Contains(ret));

			return ret;
		}
	}
}